#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
玩客云 SSD1315 OLED 驱动模块 (I2C, oled_driver_i2c.py)
封装了与 SSD1315 OLED 屏幕通过软件 I2C 通信和绘图的所有底层操作。
针对玩客云 GPIO 配置进行了适配。
修改为使用 Horizontal Addressing Mode 以优化刷新逻辑。
"""

# --- 导入必要的库 ---
import time
import numpy as np

# --- 玩客云 GPIO 配置 (软件 I2C) ---
# 物理 GPIO-431 (焊盘14) -> J2 Header Pin 32 -> gpiochip0 line 2 -> SDA
# 物理 GPIO-432 (焊盘15) -> J2 Header Pin 31 -> gpiochip0 line 3 -> SCK
OLED_SDA_CHIP = 'gpiochip0'
OLED_SDA_LINE = 2
OLED_SCK_CHIP = 'gpiochip0'
OLED_SCK_LINE = 3

# --- I2C 配置 ---
OLED_I2C_ADDR = 0x3C  # SSD1315 常见地址
OLED_CMD  = 0	#写命令
OLED_DATA = 1	#写数据

# --- OLED 屏幕参数 ---
OLED_WIDTH = 128
OLED_HEIGHT = 64
OLED_PAGES = OLED_HEIGHT // 8

# --- 全局变量 ---
# OLED 显存 (GRAM)
# 使用 NumPy 数组 [x][page]
OLED_GRAM = np.zeros((OLED_WIDTH, OLED_PAGES), dtype=np.uint8)

# --- libgpiod 对象 ---
chip_sda = None
line_sda = None
chip_sck = None
line_sck = None

# --- GPIO 初始化和清理 ---
def init_gpio():
    """初始化 GPIO 引脚为输出"""
    global chip_sda, line_sda, chip_sck, line_sck
    try:
        import gpiod
    except ImportError:
        print("oled_driver_i2c: 错误: 找不到 gpiod 模块。请安装 libgpiod 的 Python 绑定。")
        print("尝试运行: sudo apt install python3-libgpiod")
        raise

    try:
        chip_sda = gpiod.Chip(OLED_SDA_CHIP)
        line_sda = chip_sda.get_line(OLED_SDA_LINE)
        line_sda.request(consumer='oled_ssd1315', type=gpiod.LINE_REQ_DIR_OUT, default_val=0)

        chip_sck = gpiod.Chip(OLED_SCK_CHIP)
        line_sck = chip_sck.get_line(OLED_SCK_LINE)
        line_sck.request(consumer='oled_ssd1315', type=gpiod.LINE_REQ_DIR_OUT, default_val=0)

        print(f"oled_driver_i2c: GPIO 初始化成功: SDA={OLED_SDA_CHIP}:{OLED_SDA_LINE}, SCK={OLED_SCK_CHIP}:{OLED_SCK_LINE}")
    except Exception as e:
        print(f"oled_driver_i2c: GPIO 初始化失败: {e}")
        deinit_gpio()
        raise

def deinit_gpio():
    """释放 GPIO 资源"""
    global chip_sda, line_sda, chip_sck, line_sck
    if line_sda:
        try:
            line_sda.release()
        except:
            pass
    if chip_sda:
        try:
            chip_sda.close()
        except:
            pass
    if line_sck:
        try:
            line_sck.release()
        except:
            pass
    if chip_sck:
        try:
            chip_sck.close()
        except:
            pass
    print("oled_driver_i2c: GPIO 资源已释放")


# --- 软件 I2C 实现 ---
def _i2c_delay():
    """I2C 时序所需的短延迟"""
    # 根据需要调整。如果太快导致通信失败，可以取消注释下面一行。
    # time.sleep(0.000001) # 1us 延迟
    pass

def _i2c_start():
    """发送 I2C Start 条件"""
    line_sda.set_value(1)
    line_sck.set_value(1)
    _i2c_delay()
    line_sda.set_value(0)
    _i2c_delay()
    line_sck.set_value(0)
    _i2c_delay()

def _i2c_stop():
    """发送 I2C Stop 条件"""
    line_sda.set_value(0)
    line_sck.set_value(1)
    _i2c_delay()
    line_sda.set_value(1)
    _i2c_delay()

def _i2c_write_byte(byte_val):
    """写入一个字节 (不包括 ACK)"""
    for i in range(8):
        bit = (byte_val >> (7 - i)) & 1
        line_sda.set_value(bit)
        _i2c_delay()
        line_sck.set_value(1)
        _i2c_delay()
        line_sck.set_value(0)
        _i2c_delay()
    # 释放 SDA 线，准备接收 ACK/NACK
    line_sda.set_value(1)
    _i2c_delay()

def _i2c_wait_ack():
    """等待 ACK (简化处理)"""
    # 在软件 I2C 中，通常不严格检查 ACK，或者假设设备会响应
    # 这里简单地产生一个时钟脉冲
    line_sck.set_value(1)
    _i2c_delay()
    line_sck.set_value(0)
    _i2c_delay()

# --- OLED 写入函数 ---
def _write_byte(dat, mode):
    """向 OLED 写入一个字节 (命令或数据)"""
    _i2c_start()
    _i2c_write_byte(OLED_I2C_ADDR << 1) # 写地址 (Co=1, D/C#=0)
    _i2c_wait_ack()
    if mode == OLED_DATA:
        _i2c_write_byte(0x40) # Co=0, D/C#=1 (数据)
    else:
        _i2c_write_byte(0x00) # Co=0, D/C#=0 (命令)
    _i2c_wait_ack()
    _i2c_write_byte(dat)
    _i2c_wait_ack()
    _i2c_stop()

def write_cmd(cmd):
    """发送命令到 OLED"""
    _write_byte(cmd, OLED_CMD)

def write_data(data):
    """发送数据到 OLED"""
    _write_byte(data, OLED_DATA)

# --- OLED 初始化 ---
def init():
    """初始化 SSD1315 OLED (使用 Horizontal Addressing Mode)"""
    print("oled_driver_i2c: 正在初始化 OLED (Horizontal Addressing Mode)...")
    time.sleep(0.1) # 确保电源稳定
    # SSD1315 推荐的初始化序列 (修改了寻址模式)
    init_cmds = [
        0xAE, # Display Off
        0x00, # Set Lower Column Start Address
        0x10, # Set Higher Column Start Address
        0x40, # Set Display Start Line
        0x81, # Set Contrast Control
        0xCF, # Contrast Value
        0xA1, # Set Segment Re-map
        0xC8, # Set COM Output Scan Direction
        0xA6, # Set Normal/Inverse Display
        0xA8, # Set Multiplex Ratio
        0x3f, # Multiplex Ratio Value (1/64 Duty)
        0xD3, # Set Display Offset
        0x00, # Display Offset Value
        0xd5, # Set Display Clock Divide Ratio/Oscillator Frequency
        0x80, # Divide Ratio/Oscillator Frequency Value
        0xD9, # Set Pre-charge Period
        0xF1, # Pre-charge Period Value
        0xDA, # Set COM Pins Hardware Configuration
        0x12, # COM Pins Hardware Configuration Value
        0xDB, # Set VCOMH Deselect Level
        0x30, # VCOMH Deselect Level Value
        # --- 修改寻址模式为 Horizontal ---
        0x20, # Set Memory Addressing Mode
        0x00, # 0x00 = Horizontal Addressing Mode
        # --- 可选：设置默认显示区域 ---
        0x21, # Set Column Address
        0x00, # Column Start Address
        0x7F, # Column End Address (127)
        0x22, # Set Page Address
        0x00, # Page Start Address
        0x07, # Page End Address (7)
        # -------------------------------
        0x8D, # Set Charge Pump
        0x14, # Charge Pump Value
    ]
    for cmd in init_cmds:
        write_cmd(cmd)

    clear() # Clear display
    write_cmd(0xAF) # Display On
    print("oled_driver_i2c: OLED 初始化完成 (Horizontal Addressing Mode)")


# --- OLED 显存操作 ---
def clear():
    """清空 OLED 显存 (GRAM) 并刷新屏幕"""
    global OLED_GRAM
    OLED_GRAM.fill(0)
    refresh_gram()

def fill():
    """填充 OLED 显存 (GRAM) 并刷新屏幕"""
    global OLED_GRAM
    OLED_GRAM.fill(0xFF)
    refresh_gram()

# --- 刷新 GRAM 到 OLED (修改为 Horizontal Addressing Mode) ---
def refresh_gram():
    """
    将整个 GRAM 数据刷新到 OLED 屏幕 (使用 Horizontal Addressing Mode)
    """
    # 1. 设置内存地址范围 (刷新整个屏幕)
    write_cmd(0x21) # Set Column Address
    write_cmd(0x00) # Column Start Address
    write_cmd(0x7F) # Column End Address (127)
    write_cmd(0x22) # Set Page Address
    write_cmd(0x00) # Page Start Address
    write_cmd(0x07) # Page End Address (7)

    # 2. 开始连续数据写入
    _i2c_start()
    _i2c_write_byte(OLED_I2C_ADDR << 1) # 写地址 (Co=1, D/C#=0)
    _i2c_wait_ack()
    _i2c_write_byte(0x40) # Co=0, D/C#=1 (数据流开始)
    _i2c_wait_ack()

    # 3. 按顺序发送所有数据 (按页，每页按列)
    #    在 Horizontal Mode 下，控制器会自动处理地址递增
    #    顺序是：Page 0 (Col 0-127) -> Page 1 (Col 0-127) -> ... -> Page 7 (Col 0-127)
    for page in range(OLED_PAGES): # 0 to 7
        for col in range(OLED_WIDTH): # 0 to 127
            _i2c_write_byte(OLED_GRAM[col, page]) # 注意：访问方式是 [col, page]
            _i2c_wait_ack()

    _i2c_stop()
    # print("oled_driver_i2c: GRAM 已刷新 (Horizontal Mode)") # 可选调试信息

# --- 绘图函数 ---
def draw_point(x, y, t):
    """在 GRAM 缓冲区绘制或清除一个点"""
    if x < 0 or x >= OLED_WIDTH or y < 0 or y >= OLED_HEIGHT:
        return
    page = y // 8
    bit = y % 8
    if t == 1:
        OLED_GRAM[x, page] |= (1 << bit) # 注意：访问方式是 [x, page]
    else:
        OLED_GRAM[x, page] &= ~(1 << bit) # 注意：访问方式是 [x, page]

# --- 字符和字符串显示函数 ---
def show_char(x, y, char, font_data_module, size=16, mode=1):
    """
    在 OLED 上显示一个字符 (假设 8x16 字体 asc2_1608)
    :param x: 起始 X 坐标
    :param y: 起始 Y 坐标 (像素)
    :param char: 要显示的字符
    :param font_data_module: 包含 asc2_1608 字体数据的模块 (如 font_data)
    :param size: 字符大小 (仅支持 16)
    :param mode: 显示模式 (1=正常, 0=反色)
    """
    if size != 16: # 仅支持 16x8 字体
        print(f"oled_driver_i2c: 警告: 仅支持 16x8 字体 (size=16), 你提供了 {size}")
        return
    try:
        ascii_code = ord(char)
    except TypeError:
        print(f"oled_driver_i2c: 无效字符: {char}")
        return
    if ascii_code < 32 or ascii_code > 126:
         print(f"oled_driver_i2c: 警告: 字符 '{char}' (ASCII {ascii_code}) 不在支持的范围内 (32-126)")
         return # 不支持的字符

    char_index = ascii_code - 32
    # 检查索引是否有效
    if char_index < 0 or char_index >= len(font_data_module.asc2_1608):
        print(f"oled_driver_i2c: 字符 '{char}' (ASCII {ascii_code}) 超出字体数据范围")
        return

    # 获取字模数据
    char_data = font_data_module.asc2_1608[char_index]

    # 计算起始页 (每个 8x16 字符占 2 页)
    start_page = y // 8
    if start_page >= OLED_PAGES - 1: # 需要两个页面
        print(f"oled_driver_i2c: 警告: 字符 '{char}' 超出屏幕垂直范围")
        return

    try:
        for i in range(16): # 16 列
            if x + i >= OLED_WIDTH:
                break
            # 获取上半部分 (前8字节) 和下半部分 (后8字节) 数据
            col_data_upper = char_data[i] if i < 8 else 0
            col_data_lower = char_data[i + 8] if (i + 8) < len(char_data) else 0

            # 绘制上半部分到指定页 (start_page)
            for j in range(8): # 8 行
                if (start_page * 8 + j) >= OLED_HEIGHT: # 检查绝对像素坐标
                    break
                pixel = (col_data_upper >> j) & 1
                target_pixel = pixel if mode == 1 else 1 - pixel
                draw_point(x + i, start_page * 8 + j, target_pixel)

            # 绘制下半部分到下一页 (start_page + 1)
            for j in range(8): # 8 行 (对应第 8 到 15 行)
                if ((start_page + 1) * 8 + j) >= OLED_HEIGHT: # 检查绝对像素坐标
                    break
                pixel = (col_data_lower >> j) & 1
                target_pixel = pixel if mode == 1 else 1 - pixel
                draw_point(x + i, (start_page + 1) * 8 + j, target_pixel)

    except (IndexError, AttributeError, TypeError) as e:
        print(f"oled_driver_i2c: 显示字符 '{char}' 时出错: {e}")

def show_string(x, y, text, font_data_module, size=16, mode=1):
    """
    在 OLED 上显示一个字符串
    :param x: 起始 X 坐标
    :param y: 起始 Y 坐标 (像素)
    :param text: 要显示的字符串
    :param font_data_module: 包含 asc2_1608 字体数据的模块 (如 font_data)
    :param size: 字符大小 (仅支持 16)
    :param mode: 显示模式 (1=正常, 0=反色)
    """
    current_x = x
    char_width_px = 8 # 8x16 字体宽度
    for char in text:
        ascii_code = ord(char)
        if 32 <= ascii_code <= 126:
            show_char(current_x, y, char, font_data_module, size, mode) # y 是像素坐标
            current_x += char_width_px
            if current_x >= OLED_WIDTH:
                break # 防止越界
        else:
            print(f"oled_driver_i2c: 警告: 字符 '{char}' (ASCII {ascii_code}) 不在支持的范围内 (32-126)")




